﻿using System;
using System.Threading;
using Pfz.Threading.Cooperative;

namespace Pfz.Threading.Unsafe
{
	/// <summary>
	/// Class that creates a Timer that runs an action at given intervals.
	/// The intervals are always counted after the end of the last action, so it does not stack or tries to
	/// compensate delays.
	/// </summary>
	public sealed class UnsafeNonStackingTimer:
		IAdvancedDisposable
	{
		private readonly ManagedManualResetEvent _disposedEvent = new ManagedManualResetEvent();

		/// <summary>
		/// Creates a new timer that will run the given action at the given interval milliseconds.
		/// If exclusiveThreadName is not null, then a new Thread will be created. Otherwise, a 
		/// UnlimitedThreadPool thread will be used.
		/// </summary>
		public UnsafeNonStackingTimer(Action action, int interval, bool runImmediatelly=false, string exclusiveThreadName=null)
		{
			if (action == null)
				throw new ArgumentNullException("action");

			if (interval < 1)
				throw new ArgumentOutOfRangeException("interval must be at least 1.");

			Action = action;
			_interval = interval;

			if (exclusiveThreadName == null)
				UnlimitedThreadPool.Run(_Run, (object)runImmediatelly);
			else
			{
				Thread thread = new Thread(_Run);
				thread.Name = exclusiveThreadName;
				thread.Start(runImmediatelly);
			}
		}

		/// <summary>
		/// Releases the Timer thread immediatelly.
		/// Note that, if such thread is already running, it will continue to the end of the action,
		/// and only then will be returned to the pool, but the Timer will return immediatelly.
		/// </summary>
		public void Dispose()
		{
			_disposedEvent.Set();
		}

		/// <summary>
		/// Throws an exception if this object was already disposed.
		/// </summary>
		private void _CheckUndisposed()
		{
			if (WasDisposed)
				throw new ObjectDisposedException(GetType().FullName);
		}

		/// <summary>
		/// Gets a value indicating if this object was already disposed.
		/// </summary>
		public bool WasDisposed
		{
			get
			{
				return _disposedEvent.WasDisposed;
			}
		}

		/// <summary>
		/// Gets the action that's run by this timer.
		/// </summary>
		public Action Action { get; private set; }

        private int _interval;
		/// <summary>
		/// Gets the interval in which the action is run.
		/// </summary>
		public int Interval
        {
            get
            {
                return _interval;
            }
            set
            {
                if (value < 1)
                    throw new ArgumentException("Interval must be at least 1.");

                _interval = value;
            }
        }

		private void _Run(object runImmediatelly)
		{
			var manualResetEvent = _disposedEvent;
			var currentThread = Thread.CurrentThread;

			try
			{
				if ((bool)runImmediatelly)
					Action();

				while(true)
				{
					currentThread.IsBackground = true;
					if (manualResetEvent.WaitOne(_interval))
						return;

					currentThread.IsBackground = false;
					Action();
				}
			}
			finally
			{
				currentThread.IsBackground = false;

				manualResetEvent.Dispose();
			}
		}
	}
}
